package de.jmda.mview.fx.node.typeshape;

import java.util.Collection;
import java.util.LinkedList;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;

import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;

import org.apache.log4j.Logger;

import de.jmda.fx.node.behaviour.BehaviourHighlightingOnMouseEntered;
import de.jmda.fx.node.behaviour.BehaviourHighlightingOnMouseExited;
import de.jmda.fx.node.behaviour.BehaviourMouseDragging;
import de.jmda.fx.node.behaviour.BehaviourMouseDraggingEvents;
import de.jmda.fx.node.behaviour.BehaviourMouseDraggingStart;
import de.jmda.fx.node.behaviour.BehaviourMouseDraggingStop;
import de.jmda.fx.node.behaviour.Draggable;
import de.jmda.fx.node.behaviour.Highlightable;
import de.jmda.mview.typeshape.DisplayOptionsModelUtil;

public class Typeshape
		extends VBox
		implements Highlightable, Draggable
{
	private final static Logger LOGGER = Logger.getLogger(Typeshape.class);

	private TypeElement typeElement;
	private DisplayOptionsModelUtil displayOptionsModelUtil;

	private Title title;
	private Fields fields;
	private Methods methods;

	/**
	 * clients can add anchors ({@link #addTop(Anchor)}), typeshape relocates
	 * anchors automatically
	 */
	private Collection<Anchor> anchorsTop = new LinkedList<Anchor>();

	/**
	 * clients can add anchors ({@link #addBottom(Anchor)}), typeshape relocates
	 * anchors automatically
	 */
	private Collection<Anchor> anchorsBottom = new LinkedList<Anchor>();

	public Typeshape(
			TypeElement typeElement, DisplayOptionsModelUtil displayOptionsModelUtil)
	{
		super();

		this.typeElement = typeElement;
		this.displayOptionsModelUtil = displayOptionsModelUtil;
		
		init();
	}

	@Override
  public void highlightingOn()
  {
		setFocused(true);
  }

	@Override
  public void highlightingOff()
  {
		setFocused(false);
  }
	
	public TypeElement getTypeElement()
	{
		return typeElement;
	}

	public FieldView getFieldView(VariableElement field)
  {
	  return fields.getFieldView(field);
  }

	@Override
  public void activateHighlighting()
  {
		new BehaviourHighlightingOnMouseEntered<MouseEvent>(this, this);
		new BehaviourHighlightingOnMouseExited<MouseEvent>(this, this);
  }

	@Override
  public void deactivateHighlighting()
  {
		LOGGER.warn("not (yet) implemented");
  }

	@Override
  public void activateDragging()
  {
		BehaviourMouseDraggingEvents behaviourMouseDraggingEvents =
				new BehaviourMouseDraggingEvents();
		new BehaviourMouseDraggingStart(this, behaviourMouseDraggingEvents);
		new BehaviourMouseDraggingStop( this, behaviourMouseDraggingEvents);
		new BehaviourMouseDragging(     this, behaviourMouseDraggingEvents);
  }

	@Override
  public void deactivateDragging()
  {
		LOGGER.warn("not (yet) implemented");
  }

	public void addTop(Anchor anchor)
	{
		anchorsTop.add(anchor);
		relocateAnchors();
	}

	public void addBottom(Anchor anchor)
	{
		anchorsBottom.add(anchor);
		relocateAnchors();
	}
	
	private void relocateAnchors()
	{
		relocateAnchors(boundsInParentProperty().get().getMinY(), anchorsTop);
		// somehow y is a bit too big
		relocateAnchors(boundsInParentProperty().get().getMaxY() - 5, anchorsBottom);
//		relocateAnchors(boundsInLocalProperty().get().getMinY(), anchorsTop);
//		relocateAnchors(boundsInLocalProperty().get().getMaxY(), anchorsBottom);
	}

	private void relocateAnchors(double y, Collection<Anchor> anchors)
  {
		int numberOfAnchors = anchors.size();
		
		double width = boundsInParentProperty().get().getWidth();
//		double width = boundsInLocalProperty().get().getWidth();
		double distance = width / (numberOfAnchors + 1);
		double x = boundsInParentProperty().get().getMinX();
//		double x = boundsInLocalProperty().get().getMinX();
		
		for (Anchor anchor : anchors)
		{
			x += distance;
			anchor.setPoint(new Point2D(x, y));
//			anchor.pointProperty().set(new Point2D(x, y));
		}
  }

	private void init()
	{
		getStyleClass().add("typeshape");
		
		title = new Title(typeElement);
		fields = new Fields(typeElement, displayOptionsModelUtil, this);
		methods = new Methods(typeElement, displayOptionsModelUtil);
		
		getChildren().add(title);
		getChildren().add(fields);
		getChildren().add(methods);
		
		activateDragging();
		activateHighlighting();

		// relocate anchors when bounds change
		boundsInParentProperty().addListener
		(
				new ChangeListener<Bounds>()
				{
					@Override
          public void changed(
          		ObservableValue<? extends Bounds> source,
              Bounds oldValue, Bounds newValue)
          {
						relocateAnchors();
          }
				}
		);
	}

	public static class Anchor
	{
		private ObjectProperty<Point2D> point = new SimpleObjectProperty<Point2D>();
		public Point2D getPoint() { return point.get(); }
		private void setPoint(Point2D point) { this.point.setValue(point); }
		public ObjectProperty<Point2D> pointProperty() { return point; }
	}
}